/*
 * This file is part of ddesktop: remote desktop system for iPad 
 *
 * Copyright (C) 2010 Kazuhiro Satoh
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. if not, see <http://www.gnu.org/licenses/>.
 */


#include "stdafx.h"
#include "UIEventHandler.h"

#pragma unmanaged

#include <windows.h>
#include <stdio.h>
#include "../../include/constants.h"


namespace DDesktopServer{

	UIEventHandler::UIEventHandler(void)
	{
/*		AllocConsole();
		freopen("CONOUT$", "w", stdout);
		freopen("CONIN$", "r", stdin);
*/	}


	UIEventHandler::~UIEventHandler()
	{
	}


	bool UIEventHandler::AddEvent(int type, int owner, int param1, int param2)
	{
		//printf("AddEvent: type=%d, owner=%d, param1=%d, param2=%d\n", type, owner, param1, param2);
		if(type == DD_KEYBOARD_PUSH){	
			if(m_event_queue.Remain() >= 2){
				m_event_queue.Push(owner, DD_KEYBOARD_DOWN, param1, param2);
				m_event_queue.Push(owner, DD_KEYBOARD_UP, param1, param2);
			}
			else{
				return false;
			}
		}
		else{
			if(!m_event_queue.IsFull()){
				if(type == DD_MOUSE_BUTTON_MOVE){
					m_mouse_move_event_queue.Push(owner, type, param1, param2);
				}
				else if(type == DD_MOUSE_WHEEL){
					m_mouse_wheel_event_queue.Push(owner, type, param1, param2);
				}
				else if(type == DD_WINDOW_MOVE){
					m_window_move_event_queue.Push(owner, type, param1, param2);
				}
				else if(type == DD_WINDOW_RESIZE){
					m_window_resize_event_queue.Push(owner, type, param1, param2);
				}
				m_event_queue.Push(owner, type, param1, param2);
			}
			else{
				return false;
			}
		}

		return true;
	}


	bool UIEventHandler::Exec(int frame_ct)
	{
		int num_exec = 0;

		while(num_exec < 1){
			Event* e = m_event_queue.GetFront();
			if(e == NULL){
				break;
			}

			switch(e->type){
			case DD_MOUSE_BUTTON_MOVE:
				if(m_mouse_move_event_queue.Count() == 1){
					ExecOneEvent(*e);
					num_exec++;
				}
				m_event_queue.IncReadPt();
				m_mouse_move_event_queue.IncReadPt();
				break;
			case DD_MOUSE_WHEEL:
			case DD_WINDOW_MOVE:
			case DD_WINDOW_RESIZE:
				{
					EventQueue* sub_queue;
					switch(e->type){
					case DD_MOUSE_WHEEL:   sub_queue = &m_mouse_wheel_event_queue; break;
					case DD_WINDOW_MOVE:   sub_queue = &m_window_move_event_queue; break;
					case DD_WINDOW_RESIZE: sub_queue = &m_window_resize_event_queue; break;
					}

					if(sub_queue->Count() == 1){
						ExecOneEvent(*sub_queue->GetFront());
						num_exec++;
					}
					else{
						Event* front = sub_queue->GetFront();
						Event* last = sub_queue->GetLast();
						last->param1 += front->param1;
						last->param2 += front->param2;
					}
					m_event_queue.IncReadPt();
					sub_queue->IncReadPt();
				}
				break;
			default:
				ExecOneEvent(*e);
				m_event_queue.IncReadPt();
				num_exec++;
				break;
			}
		}

		return true;
	}


	bool UIEventHandler::ExecOneEvent(Event& e)
	{
		//printf("ExecEventt: type=%d, owner=%d, param1=%d(0x%02x), param2=%d\n", e.type, e.owner, e.param1, e.param1, e.param2);

		switch(e.type){
		case DD_MOUSE_BUTTON_UP:
		case DD_MOUSE_BUTTON_DOWN:
		case DD_MOUSE_BUTTON_MOVE:
			{
				POINT mouse_pos;
				int mouse_event_type;
				mouse_pos.x = ((DWORD)e.param1* WORD(-1) / ::GetSystemMetrics(SM_CXSCREEN));
				mouse_pos.y = ((DWORD)e.param2 * WORD(-1) / ::GetSystemMetrics(SM_CYSCREEN));
				switch(e.type){
				case DD_MOUSE_BUTTON_UP:
					switch(e.owner){
					case DD_MOUSE_LEFT_BUTTON:   mouse_event_type = MOUSEEVENTF_LEFTUP;		break;
					case DD_MOUSE_RIGHT_BUTTON:  mouse_event_type = MOUSEEVENTF_RIGHTUP;		break;
					case DD_MOUSE_MIDDLE_BUTTON: mouse_event_type = MOUSEEVENTF_MIDDLEUP;		break;
					}
					break;
				case DD_MOUSE_BUTTON_DOWN:
					switch(e.owner){
					case DD_MOUSE_LEFT_BUTTON:   mouse_event_type = MOUSEEVENTF_LEFTDOWN;		break;
					case DD_MOUSE_RIGHT_BUTTON:  mouse_event_type = MOUSEEVENTF_RIGHTDOWN;		break;
					case DD_MOUSE_MIDDLE_BUTTON: mouse_event_type = MOUSEEVENTF_MIDDLEDOWN;	break;
					}
					break;
				case DD_MOUSE_BUTTON_MOVE:
					mouse_event_type = MOUSEEVENTF_MOVE;
					break;
				}
				INPUT input = { INPUT_MOUSE, mouse_pos.x, mouse_pos.y, 0, mouse_event_type | MOUSEEVENTF_ABSOLUTE, 0, 0 };
				::SendInput(1, &input, sizeof(INPUT));
			}
			break;
		case DD_KEYBOARD_DOWN:
		case DD_KEYBOARD_UP:
			{
				int event_type;
				unsigned char vkcode;
				unsigned char shift;
				if(e.param1 < 0x80){
					short vkcodescan = ::VkKeyScan(e.param1);
					vkcode = vkcodescan & 0xFF;
					shift = (vkcodescan >> 8) % 0xFF;
					if(vkcode == -1){
						return false;
					}
				}
				else{
					shift = 0;
					vkcode = -1;
					switch(e.param1){
					case DD_KEY_BACKSPACE:	vkcode = VK_BACK;		break;
					case DD_KEY_TAB:		vkcode = VK_TAB;		break;
					case DD_KEY_RETURN:		vkcode = VK_RETURN;		break;
					case DD_KEY_SHIFT:		vkcode = VK_SHIFT;		break;
					case DD_KEY_CONTROL:	vkcode = VK_CONTROL;	break;
					case DD_KEY_ALT:		vkcode = VK_MENU;		break;
					case DD_KEY_ESCAPE:		vkcode = VK_ESCAPE;		break;
					case DD_KEY_SPACE:		vkcode = VK_SPACE;		break;
					case DD_KEY_PAGEUP:		vkcode = VK_PRIOR;		break;
					case DD_KEY_PAGEDOWN:	vkcode = VK_NEXT;		break;
					case DD_KEY_END:		vkcode = VK_END;		break;
					case DD_KEY_HOME:		vkcode = VK_HOME;		break;
					case DD_KEY_LEFT:		vkcode = VK_LEFT;		break;
					case DD_KEY_UP:			vkcode = VK_UP;			break;
					case DD_KEY_RIGHT:		vkcode = VK_RIGHT;		break;
					case DD_KEY_DOWN:		vkcode = VK_DOWN;		break;
					case DD_KEY_INSERT:		vkcode = VK_INSERT;		break;
					case DD_KEY_DELETE:		vkcode = VK_DELETE;		break;
					case DD_KEY_HANKAKU_ZENKAKU: vkcode = VK_OEM_AUTO; break;

					case DD_KEY_MAXIMIZE_WINDOW:
						if(e.type == DD_KEYBOARD_UP){
							HWND active_window = ::GetForegroundWindow();
							if(active_window == NULL){
								return false;
							}
							::ShowWindow(active_window, SW_MAXIMIZE);
						}
						break;
					case DD_KEY_MINIMIZE_WINDOW:
						if(e.type == DD_KEYBOARD_UP){
							HWND active_window = ::GetForegroundWindow();
							if(active_window == NULL){
								return false;
							}
							::ShowWindow(active_window, SW_MINIMIZE);
						}
						break;
					case DD_KEY_CLOSE_WINDOW:	// ALT + F4
						vkcode = VK_F4;
						shift = 4;
						break;
					case DD_KEY_CTRL_ALT_DEL:	// CTRL + ALT + DEL
						vkcode = VK_DELETE;
						shift = 2 | 4;
						break;
					case DD_KEY_UNDO:	// CTRL + 'z'
						vkcode = 'Z';
						shift = 2;
						break;
					case DD_KEY_CUT:	// CTRL + 'x'
						vkcode = 'X';
						shift = 2;
						break;
					case DD_KEY_COPY:	// CTRL + 'c'
						vkcode = 'C';
						shift = 2;
						break;
					case DD_KEY_PASTE:	// CTRL + 'V'
						vkcode = 'V';
						shift = 2;
						break;

					}

					if(vkcode == -1){
						return true;
					}
				}

				if(e.type == DD_KEYBOARD_DOWN){
					event_type = 0;
				}
				else{
					event_type = KEYEVENTF_KEYUP;
				}

				INPUT input[4];
				int num_input = 0;

				if(shift & 1){ // shift
					input[num_input].type = INPUT_KEYBOARD;
					input[num_input].ki.wVk = VK_SHIFT;
					input[num_input].ki.wScan = 0;
					input[num_input].ki.dwFlags = event_type;
					input[num_input].ki.time = 0;
					input[num_input].ki.dwExtraInfo = NULL;
					num_input++;
				}
				if(shift & 2){ // ctrl
					input[num_input].type = INPUT_KEYBOARD;
					input[num_input].ki.wVk = VK_CONTROL;
					input[num_input].ki.wScan = 0;
					input[num_input].ki.dwFlags = event_type;
					input[num_input].ki.time = 0;
					input[num_input].ki.dwExtraInfo = NULL;
					num_input++;
				}
				if(shift & 4){ // alt
					input[num_input].type = INPUT_KEYBOARD;
					input[num_input].ki.wVk = VK_MENU;
					input[num_input].ki.wScan = 0;
					input[num_input].ki.dwFlags = event_type;
					input[num_input].ki.time = 0;
					input[num_input].ki.dwExtraInfo = NULL;
					num_input++;
				}

				{
					input[num_input].type = INPUT_KEYBOARD;
					input[num_input].ki.wVk = vkcode;
					input[num_input].ki.wScan = 0;
					input[num_input].ki.dwFlags = event_type;
					input[num_input].ki.time = 0;
					input[num_input].ki.dwExtraInfo = NULL;
					num_input++;
				}
				::SendInput(num_input, input, sizeof(INPUT));
			}
			break;
		case DD_MOUSE_WHEEL:
			{
				POINT mouse_pos;
				::GetCursorPos(&mouse_pos);
				INPUT input = { INPUT_MOUSE, mouse_pos.x, mouse_pos.y, e.param2, MOUSEEVENTF_WHEEL | MOUSEEVENTF_ABSOLUTE, 0, 0 };
				::SendInput(1, &input, sizeof(INPUT));
			}
			break;
		case DD_WINDOW_MOVE:
		case DD_WINDOW_RESIZE:
			{
				RECT r;
				HWND active_window = ::GetForegroundWindow();
				if(active_window == NULL){
					return false;
				}
				if(::GetWindowRect(active_window, &r) == 0){
					return false;
				}

				r.right += e.param1;
				r.bottom += e.param2;
				if(e.type == DD_WINDOW_MOVE){
					r.left += e.param1;
					r.top += e.param2;
				}

				if(::MoveWindow(active_window, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE) == 0){
					return false;
				}
			}
			break;
		case DD_WINDOW_MAXIMIZE:
			{
				HWND active_window = ::GetForegroundWindow();
				if(active_window == NULL){
					return false;
				}
				::ShowWindow(active_window, SW_MAXIMIZE);
			}
			break;
		case DD_WINDOW_MINIMIZE:
			{
				HWND active_window = ::GetForegroundWindow();
				if(active_window == NULL){
					return false;
				}
				::ShowWindow(active_window, SW_MINIMIZE);
			}
			break;
		case DD_WINDOW_CLOSE:
			{
				HWND active_window = ::GetForegroundWindow();
				if(active_window == NULL){
					return false;
				}
				if(::SendMessage(active_window, WM_CLOSE, 0, 0) == 0){
					return false;
				}
			}
			break;
		}

		return true;
	}


	// EveneQueue
	UIEventHandler::EventQueue::EventQueue(void)
	{
		m_read_pt = 0;
		m_write_pt = 0;
	}


	UIEventHandler::EventQueue::~EventQueue()
	{
	}


	bool UIEventHandler::EventQueue::IsEmpty(void) const
	{
		if(m_read_pt == m_write_pt){
			return true;
		}
		else{
			return false;
		}
	}

	bool UIEventHandler::EventQueue::IsFull(void) const
	{
		int next = (m_write_pt + 1) % EVENT_QUEUE_SIZE;

		if(next == m_read_pt){
			return true;
		}
		else{
			return false;
		}
	}


	int UIEventHandler::EventQueue::Remain(void) const
	{
		return EVENT_QUEUE_SIZE - 1 - Count();
	}


	int UIEventHandler::EventQueue::Count(void) const
	{
		return (m_write_pt + EVENT_QUEUE_SIZE - m_read_pt) % EVENT_QUEUE_SIZE;
	}


	void UIEventHandler::EventQueue::IncReadPt(void)
	{
		m_read_pt = (m_read_pt + 1) % EVENT_QUEUE_SIZE;
	}


	void UIEventHandler::EventQueue::IncWritePt(void)
	{
		m_write_pt = (m_write_pt + 1) % EVENT_QUEUE_SIZE;
	}


	bool UIEventHandler::EventQueue::Push(int owner, int type, int param1, int param2)
	{
		if(!IsFull()){
			m_queue[m_write_pt].owner = owner;
			m_queue[m_write_pt].type = type;
			m_queue[m_write_pt].param1 = param1;
			m_queue[m_write_pt].param2 = param2;
			m_queue[m_write_pt].canceled = false;
			IncWritePt();
			return true;
		}
		else{
			return false;
		}
	}

	UIEventHandler::Event* UIEventHandler::EventQueue::GetFront(void)
	{
		if(!IsEmpty()){
			return &m_queue[m_read_pt];
		}
		else{
			return NULL;
		}
	}

	UIEventHandler::Event* UIEventHandler::EventQueue::GetLast(void)
	{
		if(!IsEmpty()){
			return &m_queue[(m_write_pt + EVENT_QUEUE_SIZE - 1) % EVENT_QUEUE_SIZE];
		}
		else{
			return NULL;
		}
	}
};
